package in.co.codedoc.cg.velocity;

import java.io.IOException;
import java.io.Writer;
import java.lang.annotation.Annotation;

import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.parser.node.Node;

import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.type.ArrayType;
import com.sun.mirror.type.PrimitiveType;
import com.sun.mirror.type.ReferenceType;
import com.sun.mirror.type.TypeMirror;

public class MethodParameterAccessBlockDirective extends CGBlockDirective
{
	public MethodParameterAccessBlockDirective()
	{
		super("parameter object");
	}
	
	@Override
	public String getName()
	{
		return "MethodParameterAccessBlock";
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean render(InternalContextAdapter context, Writer writer,
			Node node) throws IOException, ResourceNotFoundException,
			ParseErrorException, MethodInvocationException
	{
		ParameterDeclaration parameter = (ParameterDeclaration) node.jjtGetChild(0).value(context);
		
		context.put("castType",GetCastTypeName(parameter.getType()));
		context.put("jsonType",GetJsonTypeName(parameter.getType()));
		
		String annotationValueName = null;
		
		if(node.jjtGetNumChildren() > 2)
		{
			String annotationTypeName = node.jjtGetChild(1).value(context).toString();
			annotationValueName = node.jjtGetChild(2).value(context).toString();
			
			Annotation anno = null;
			try
			{
				anno = parameter.getAnnotation
						((Class<? extends Annotation>) Class.forName(annotationTypeName));
			}
			catch (Throwable th)
			{
				throw new RuntimeException
				(
					"Failed to read parameter annotation " + annotationTypeName + 
					" for param " + parameter.getSimpleName() + ":" + th.getMessage(),
					th
				);
			}
			
			if(anno != null)
			{
				context.put(annotationValueName,anno);
			}
		}
		
		try
		{
			node.jjtGetChild(node.jjtGetNumChildren()-1).render(context, writer);
		}
		finally
		{
			context.remove("jsonType");
			context.remove("castType");
			if(annotationValueName != null)
			{
				context.remove(annotationValueName);
			}
		}
		
		return true;
	}

	private Object GetCastTypeName(TypeMirror type)
	{
		if(type instanceof PrimitiveType)
		{
			return type.toString();
		}
		else if(type instanceof ArrayType)
		{
			return type.toString();
		}
		else if(type instanceof ReferenceType)
		{
			return type.toString();
		}
		else
		{
			throw new RuntimeException("(3)Unhandled type " + type.toString());
		}
	}

	private String GetJsonTypeName(TypeMirror type)
	{
		if(type instanceof PrimitiveType)
		{
			return GetPrimitiveJsonTypeName(type.toString());
		}
		else if(type instanceof ArrayType)
		{
			return "JSONArrayValue";
		}
		else if(type instanceof ReferenceType)
		{
			String typeName = type.toString();
			
			if(typeName.equals("java.lang.String"))
			{
				return "JSONStringValue";
			}
			else if(typeName.indexOf("java.util.List") == 0)
			{
				return "JSONArrayValue";
			}
			else
			{
				return "JSONObjectValue";
			}
		}
		else
		{
			throw new RuntimeException("(1)Unhandled type " + type.toString());
		}
	}

	private String GetPrimitiveJsonTypeName(String typeName)
	{
		if(typeName.equals("int") || typeName.equals("Integer")
			|| typeName.equals("long") || typeName.equals("Long")
			|| typeName.equals("short") || typeName.equals("Short"))
		{
			return "JSONLongValue";
		}
		else if(typeName.equals("float") || typeName.equals("Float")
				|| typeName.equals("double") || typeName.equals("Double"))
		{
			return "JSONDoubleValue";
		}
		else
		{
			throw new RuntimeException("(2)Unhandled typename " + typeName);
		}
	}
}
